package com.kingbook.core.executor;

import com.alibaba.fastjson.JSONArray;
import com.kingbook.common.utils.StringUtils;
import com.kingbook.core.cache.CacheSupport;
import com.kingbook.core.exception.DatabaseAccessException;
import com.kingbook.core.result.JSONPageResult;
import com.kingbook.core.result.JSONResult;
import com.kingbook.core.sql.SqlContext;
import com.kingbook.core.sql.SqlFactory;
import com.kingbook.core.sql.SqlPageContext;
import com.kingbook.core.sql.dialect.DBDialect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;

/**
 * @Author: Bucky
 * @Date: 2019/1/2
 * @Version 1.0
 */
@Component
public class DatabaseOperations {

    protected static Logger logger = LoggerFactory.getLogger(DatabaseOperations.class);

    private static final String SQL_ERROR = "数据处理错误";

    @Resource
    private DatabaseSupport support;

    @Autowired
    private CacheSupport cacheSupport;

    public DBDialect getDBDialect() {
        return support.getDBDialect();
    }

    /**
     * 返回第一条结果
     */
    public <T> T findFirst(SqlContext pc, Class<T> clazz) {
        try {
            T t = cacheSupport.getCache(pc, clazz);
            if (t == null) {
                t = support.find(pc.getSql(), pc.getParamMap(), clazz);
                cacheSupport.cache(pc, t);
            }
            logResult(t == null ? 0 : 1);
            return t;
        } catch (RuntimeException e) {
            throw new DatabaseAccessException(SQL_ERROR, e, logger);
        }
    }


    /**
     * 返回所有结果
     */
    public Map<String, Object>[] queryMap(SqlContext pc) {
        try {
            Map<String, Object>[] t = cacheSupport.getCache(pc, Map[].class);
            if (t == null) {
                t = support.queryMap(pc.getSql(), pc.getParamMap());
                cacheSupport.cache(pc, t);
            }
            logResult(t == null ? 0 : t.length);
            return t;
        } catch (RuntimeException e) {
            throw new DatabaseAccessException(SQL_ERROR, e, logger);
        }
    }


    /**
     * 返回所有结果
     */
    public JSONResult query(SqlContext pc) {
        try {
            JSONArray t = cacheSupport.getCache(pc, JSONArray.class);
            if (t == null) {
                t = support.query(pc.getSql(), pc.getParamMap());
                cacheSupport.cache(pc, t);
            }
            logResult(t == null ? 0 : t.size());
            return JSONResult.success(t);
        } catch (RuntimeException e) {
            throw new DatabaseAccessException(SQL_ERROR, e, logger);
        }
    }

    /**
     * 返回所有结果
     */
    public <T> List<T> query(SqlContext pc, Class<T> clazz) {
        try {
            List<T> t = cacheSupport.getCache(pc, List.class);
            if (t == null) {
                t = support.query(pc.getSql(), pc.getParamMap(), clazz);
                cacheSupport.cache(pc, t);
            }
            logResult(t == null ? 0 : t.size());
            return t;
        } catch (RuntimeException e) {
            throw new DatabaseAccessException(SQL_ERROR, e, logger);
        }
    }

    /**
     * 分页查询
     */
    public JSONPageResult queryForPages(SqlPageContext pc, Class clazz) {
        try {
            JSONPageResult jp = cacheSupport.getCache(pc, JSONPageResult.class);
            if (jp == null) {
                int rows = support.getRows(pc.getCountSql(), pc.getCountParamMap());
                if (rows > 0) {
                    String sql = support.getDBDialect().getPaginationSQL(pc.getSql(), (pc.getPageNo() - 1) * pc.getPageSize(), pc.getPageSize());
                    if (clazz != null) {
                        List list = support.query(sql, pc.getParamMap(), clazz);
                        jp = JSONPageResult.success(pc.getPageNo(), pc.getPageSize(), rows, list);
                    } else {
                        JSONArray t = support.query(sql, pc.getParamMap());
                        jp = JSONPageResult.success(pc.getPageNo(), pc.getPageSize(), rows, t);
                    }
                    cacheSupport.cache(pc, jp);
                }else {
                    jp = JSONPageResult.success(pc.getPageNo(), pc.getPageSize(), rows, new JSONArray());
                }
            }
            logResult(jp != null ? jp.getCount() : 0);
            return jp;
        } catch (RuntimeException e) {
            throw new DatabaseAccessException(SQL_ERROR, e, logger);
        }
    }


    public int getRows(SqlContext sc) throws DatabaseAccessException {
        try {
            Integer rows = cacheSupport.getCache(sc, Integer.class);
            if (rows == null) {
                rows = support.getRows(sc.getSql(), sc.getParamMap());
                cacheSupport.cache(sc, rows);
            }
            logResult(1);
            return rows;
        } catch (RuntimeException e) {
            throw new DatabaseAccessException(SQL_ERROR, e, logger);
        }
    }

    /////////////////////////////////////////////////////////////////////////////

    public JSONResult insertReturnKey(SqlContext sqlContext) {
        try {
            int pk = support.insertReturnKey(sqlContext.getSql(), sqlContext.getParamMap());
            logger.debug("[Result] pk:{}", pk);
            if (pk > 0)
                cacheSupport.recycleCache(sqlContext);
            return JSONResult.success(String.valueOf(pk));
        } catch (RuntimeException e) {
            throw new DatabaseAccessException(SQL_ERROR, e, logger);
        }
    }

    public JSONResult update(SqlContext sqlContext) {
        try {
            int count  =  support.update(sqlContext.getSql(), sqlContext.getParamMap());
            logger.debug("[Result] count:{}", count);
            if (count > 0)
                cacheSupport.recycleCache(sqlContext);
            return JSONResult.successWithCount(count);
        } catch (RuntimeException e) {
            throw new DatabaseAccessException(SQL_ERROR, e, logger);
        }
    }

    public JSONResult batchUpdate(String... sql) throws DatabaseAccessException {
        try {
            int[] result = support.batchUpdate(sql);
            for (int i = 0; i < result.length; i++) {
                if (result[i] > 0)
                    cacheSupport.recycleCache(SqlFactory.getSqlContext(sql[i], getDBDialect(), null));
            }
            logResult(result.length);
            return JSONResult.success(StringUtils.arrayToString(result));
        } catch (RuntimeException e) {
            throw new DatabaseAccessException(SQL_ERROR, e, logger);
        }
    }

    public JSONResult batchUpdate(SqlContext sqlContext, List<Map<String, ?>> batchValues) {
        try {
            return logResultAndRecycleCache(sqlContext, support.batchUpdate(sqlContext.getSql(), batchValues));
        } catch (RuntimeException e) {
            throw new DatabaseAccessException(SQL_ERROR, e, logger);
        }
    }

    public JSONResult batchUpdate(SqlContext sqlContext, Map<String, ?>[] batchValues) {
        try {
            return logResultAndRecycleCache(sqlContext, support.batchUpdate(sqlContext.getSql(), batchValues));
        } catch (RuntimeException e) {
            throw new DatabaseAccessException(SQL_ERROR, e, logger);
        }
    }

    public JSONResult batchInsertReturnKey(SqlContext sqlContext, List<Map<String, ?>> batchValues) {
        try {
            return logKeyAndRecycleCache(sqlContext, support.batchInsertReturnKey(sqlContext.getSql(), batchValues));
        } catch (RuntimeException e) {
            throw new DatabaseAccessException(SQL_ERROR, e, logger);
        }
    }


    public JSONResult batchInsertReturnKey(SqlContext sqlContext, Map<String, ?>[] batchValues) {
        try {
            return logKeyAndRecycleCache(sqlContext, support.batchInsertReturnKey(sqlContext.getSql(), batchValues));
        } catch (RuntimeException e) {
            throw new DatabaseAccessException(SQL_ERROR, e, logger);
        }
    }

    private void logResult(int rs) {
        logger.debug("[Result] {}", rs);
    }

    private JSONResult logResultAndRecycleCache(SqlContext sqlContext, int[] rs) {
        logger.debug("[Result] {}", rs == null ? 0 : rs.length);
        for (int i = 0; i < rs.length; i++) {
            if (rs[i] > 0) {
                cacheSupport.recycleCache(sqlContext);
                break;
            }
        }
        return JSONResult.success(StringUtils.arrayToString(rs));
    }

    private JSONResult logKeyAndRecycleCache(SqlContext sqlContext, int... keys) {
        logger.debug("[Keys] {}", keys);
        for (int i = 0; i < keys.length; i++) {
            if (keys[i] > 0) {
                cacheSupport.recycleCache(sqlContext);
                break;
            }
        }
        return JSONResult.success(StringUtils.arrayToString(keys));
    }

    public DatabaseSupport getSupport() {
        return support;
    }

    public void setSupport(DatabaseSupport support) {
        this.support = support;
    }
}
